package main

import (
	"log"
	"sync"
	"time"

	"gitee.com/uxff/beatdemo/beat"
)

const (
	BotTint = 0.6
)

func main() {
	log.SetFlags(log.LstdFlags | log.Lmicroseconds)
	tStart := time.Now()
	// testRand(100000000)
	testRpcLv()
	// return
	// BenchTeam()
	// return
	//Golden,NpcB //bo success
	//Br < A < Si < B < Go < C < Le < D in 1,8,1
	// BotBattleThreatable("Br", 2, 16, 2, beat.RateNpcA, "tmp")
	// BotBattleThreatable("Si", 2, 16, 2, beat.RateNpcB, "tmp")
	// return
	//Br = A < Si = B < Go = C < Le = D in 2,6,2
	// BotBattleThreatable("Br", 22, 0, 3, beat.RateNpcA, "tmp")
	// BotBattleThreatable("Si", 22, 0, 3, beat.RateNpcB)
	// BotBattleThreatable("Go", 22, 0, 3, beat.RateNpcC)
	// BotBattleThreatable("Le", 22, 0, 3, beat.RateNpcD)
	// BotBattleThreatable("Br", 3, 19, 3, beat.RateNpcA)
	// BotBattleThreatable("Si", 3, 19, 3, beat.RateNpcB)
	// BotBattleThreatable("Go", 3, 19, 3, beat.RateNpcC)
	// BotBattleThreatable("Le", 3, 19, 3, beat.RateNpcD)
	// BotBattleThreatable("Br", 20, 0, 0, beat.RateNpcA)
	// BotBattleThreatable("Si", 20, 0, 0, beat.RateNpcA)
	// BotBattleThreatable("Go", 20, 0, 0, beat.RateNpcA)
	// BotBattleThreatable("Le", 20, 0, 0, beat.RateNpcA)
	// BotBattleThreatable("Br", 20, 0, 0, beat.RateNpcB)
	// BotBattleThreatable("Si", 20, 0, 0, beat.RateNpcB)
	// BotBattleThreatable("Go", 20, 0, 0, beat.RateNpcB)
	// BotBattleThreatable("Le", 20, 0, 0, beat.RateNpcB)
	// return

	// BotBattleThreatable("Br", 2, 6, 2, beat.RateNpcB)
	// BotBattleThreatable("Go", 2, 6, 2, beat.RateNpcD)
	// BotBattleThreatable("Le", 2, 6, 2, beat.RateNpcD)
	// // Br <= A < Si <= B < Go <= C < Le <= D in 2,16,2 (/2)
	// // BotBattleThreatable("Le", 3, 19, 3, beat.RateNpcD)
	// BotBattleThreatable("Br", 3, 19, 3, beat.RateNpcA)
	// BotBattleThreatable("Br", 3, 19, 3, beat.RateNpcB)
	// BotBattleThreatable("Go", 3, 19, 3, beat.RateNpcD)
	// BotBattleThreatable("Le", 3, 19, 3, beat.RateNpcD)
	// //A <= Br <= B < Si <= C < Go <= D < Le in 4,32,2 (/3.16)
	// BotBattleThreatable("Br", 4, 32, 4, beat.RateNpcA) //Le success
	// BotBattleThreatable("Br", 4, 32, 4, beat.RateNpcB) //Le success
	// BotBattleThreatable("Go", 4, 32, 4, beat.RateNpcD) //Le success
	// BotBattleThreatable("Le", 4, 32, 4, beat.RateNpcD) //Le success
	// // BotBattleThreatable("Si", 36, 0, 4, beat.RateNpcB) //Le success
	// BotBattleThreatable("Br", 6, 88, 6, beat.RateNpcA) //Le success
	// BotBattleThreatable("Br", 6, 88, 6, beat.RateNpcB) //Le success
	// BotBattleThreatable("Go", 6, 88, 6, beat.RateNpcD) //Le success
	// BotBattleThreatable("Le", 6, 88, 6, beat.RateNpcD) //Le success
	//BotBattleThreatable("Le", 10, 180, 10, beat.RateNpcD) //Le success

	//common contest
	BotBattleThreatable("Br", 1, 3, 1, beat.RateNpcA, "")
	BotBattleThreatable("Si", 1, 3, 1, beat.RateNpcB, "")
	BotBattleThreatable("Go", 1, 3, 1, beat.RateNpcC, "")
	BotBattleThreatable("Le", 1, 3, 1, beat.RateNpcD, "")

	BotBattleThreatable("Br", 1, 7, 2, beat.RateNpcA, "")
	BotBattleThreatable("Si", 1, 7, 2, beat.RateNpcB, "")
	BotBattleThreatable("Go", 1, 7, 2, beat.RateNpcC, "")
	BotBattleThreatable("Le", 1, 7, 2, beat.RateNpcD, "")

	BotBattleThreatable("Br", 2, 19, 4, beat.RateNpcA, "")
	BotBattleThreatable("Si", 2, 19, 4, beat.RateNpcB, "")
	BotBattleThreatable("Go", 2, 19, 4, beat.RateNpcC, "")
	BotBattleThreatable("Le", 2, 19, 4, beat.RateNpcD, "")

	BotBattleThreatable("Br", 4, 30, 6, beat.RateNpcA, "")
	BotBattleThreatable("Si", 4, 30, 6, beat.RateNpcB, "")
	BotBattleThreatable("Go", 4, 30, 6, beat.RateNpcC, "")
	BotBattleThreatable("Le", 4, 30, 6, beat.RateNpcD, "")

	// BotBattleThreatable("Br", 8, 64, 8, beat.RateNpcA)
	// BotBattleThreatable("Si", 8, 64, 8, beat.RateNpcB)
	// BotBattleThreatable("Go", 8, 64, 8, beat.RateNpcC)
	// BotBattleThreatable("Le", 8, 64, 8, beat.RateNpcD)
	//promotion contest
	BotBattleThreatable("Br", 1, 3, 1, beat.RateNpcB, "pro")
	BotBattleThreatable("Si", 1, 3, 1, beat.RateNpcC, "pro")
	BotBattleThreatable("Go", 1, 3, 1, beat.RateNpcD, "pro")
	BotBattleThreatable("Br", 1, 7, 2, beat.RateNpcB, "pro")
	BotBattleThreatable("Si", 1, 7, 2, beat.RateNpcC, "pro")
	BotBattleThreatable("Go", 1, 7, 2, beat.RateNpcD, "pro")
	BotBattleThreatable("Br", 2, 19, 4, beat.RateNpcB, "pro")
	BotBattleThreatable("Si", 2, 19, 4, beat.RateNpcC, "pro")
	BotBattleThreatable("Go", 2, 19, 4, beat.RateNpcD, "pro")
	BotBattleThreatable("Br", 4, 30, 6, beat.RateNpcB, "pro")
	BotBattleThreatable("Si", 4, 30, 6, beat.RateNpcC, "pro")
	BotBattleThreatable("Go", 4, 30, 6, beat.RateNpcD, "pro")

	// //hard promotion
	// BotBattleThreatable("Br", 1, 3, 1, beat.RateNpcC, "hard")
	// BotBattleThreatable("Si", 1, 3, 1, beat.RateNpcD, "hard")
	// BotBattleThreatable("Br", 1, 7, 1, beat.RateNpcC, "hard")
	// BotBattleThreatable("Si", 1, 7, 1, beat.RateNpcD, "hard")
	// BotBattleThreatable("Br", 2, 20, 3, beat.RateNpcC, "hard")
	// BotBattleThreatable("Si", 2, 20, 3, beat.RateNpcD, "hard")

	//single role
	// BotBattleThreatable("Br", 10, 0, 0, beat.RateNpcA, "singT")
	// BotBattleThreatable("Si", 10, 0, 0, beat.RateNpcB, "singT")
	// BotBattleThreatable("Go", 10, 0, 0, beat.RateNpcC, "singT")
	// BotBattleThreatable("Le", 10, 0, 0, beat.RateNpcD, "singT")
	// BotBattleThreatable("Br", 25, 0, 0, beat.RateNpcA, "singT")
	// BotBattleThreatable("Si", 25, 0, 0, beat.RateNpcB, "singT")
	// BotBattleThreatable("Go", 25, 0, 0, beat.RateNpcC, "singT")
	// BotBattleThreatable("Le", 25, 0, 0, beat.RateNpcD, "singT")

	// BotBattleThreatable("Br", 0, 10, 0, beat.RateNpcA, "singA")
	// BotBattleThreatable("Si", 0, 10, 0, beat.RateNpcB, "singA")
	// BotBattleThreatable("Go", 0, 10, 0, beat.RateNpcC, "singA")
	// BotBattleThreatable("Le", 0, 10, 0, beat.RateNpcD, "singA")
	// BotBattleThreatable("Br", 0, 25, 0, beat.RateNpcA, "singA")
	// BotBattleThreatable("Si", 0, 25, 0, beat.RateNpcB, "singA")
	// BotBattleThreatable("Go", 0, 25, 0, beat.RateNpcC, "singA")
	// BotBattleThreatable("Le", 0, 25, 0, beat.RateNpcD, "singA")
	// BotBattleThreatable("Br", 0, 20, 5, beat.RateNpcA, "singA")
	// BotBattleThreatable("Si", 0, 20, 5, beat.RateNpcB, "singA")
	// BotBattleThreatable("Go", 0, 20, 5, beat.RateNpcC, "singA")
	// BotBattleThreatable("Le", 0, 20, 5, beat.RateNpcD, "singA")
	// BotBattleThreatable("Br", 0, 40, 0, beat.RateNpcA, "singA")
	// BotBattleThreatable("Si", 0, 40, 0, beat.RateNpcB, "singA")
	// BotBattleThreatable("Go", 0, 40, 0, beat.RateNpcC, "singA")
	// BotBattleThreatable("Le", 0, 40, 0, beat.RateNpcD, "singA")

	// BotBattleThreatable("Br", 8, 64, 8, beat.RateNpcB)
	// BotBattleThreatable("Si", 8, 64, 8, beat.RateNpcC)
	// BotBattleThreatable("Go", 8, 64, 8, beat.RateNpcD)

	// threatless
	// peer contest
	// BotBattleThreatless("Br", 1, 7, 2, beat.RateNpcA)
	// BotBattleThreatless("Si", 1, 7, 2, beat.RateNpcB)
	// BotBattleThreatless("Go", 1, 7, 2, beat.RateNpcC)
	// BotBattleThreatless("Le", 1, 7, 2, beat.RateNpcD)

	// BotBattleThreatless("Br", 2, 20, 3, beat.RateNpcA)
	// BotBattleThreatless("Si", 2, 20, 3, beat.RateNpcB)
	// BotBattleThreatless("Go", 2, 20, 3, beat.RateNpcC)
	// BotBattleThreatless("Le", 2, 20, 3, beat.RateNpcD)

	// BotBattleThreatless("Br", 4, 32, 4, beat.RateNpcA)
	// BotBattleThreatless("Si", 4, 32, 4, beat.RateNpcB)
	// BotBattleThreatless("Go", 4, 32, 4, beat.RateNpcC)
	// BotBattleThreatless("Le", 4, 32, 4, beat.RateNpcD)

	// BotBattleThreatless("Br", 8, 64, 8, beat.RateNpcA)
	// BotBattleThreatless("Si", 8, 64, 8, beat.RateNpcB)
	// BotBattleThreatless("Go", 8, 64, 8, beat.RateNpcC)
	// BotBattleThreatless("Le", 8, 64, 8, beat.RateNpcD)

	//promotion contest
	// BotBattleThreatless("Br", 1, 7, 2, beat.RateNpcB)
	// BotBattleThreatless("Si", 1, 7, 2, beat.RateNpcC)
	// BotBattleThreatless("Go", 1, 7, 2, beat.RateNpcD)
	// BotBattleThreatless("Br", 2, 20, 3, beat.RateNpcB)
	// BotBattleThreatless("Si", 2, 20, 3, beat.RateNpcC)
	// BotBattleThreatless("Go", 2, 20, 3, beat.RateNpcD)
	// BotBattleThreatless("Br", 4, 32, 4, beat.RateNpcB)
	// BotBattleThreatless("Si", 4, 32, 4, beat.RateNpcC)
	// BotBattleThreatless("Go", 4, 32, 4, beat.RateNpcD)

	// BotBattleThreatless("Br", 8, 64, 8, beat.RateNpcB)
	// BotBattleThreatless("Si", 8, 64, 8, beat.RateNpcC)
	// BotBattleThreatless("Go", 8, 64, 8, beat.RateNpcD)

	log.Printf("use time %v", time.Now().Sub(tStart))
	time.Sleep(time.Millisecond * 10)
}

//single thread, TODO multi thread
func BenchTeam() {
	teamA := genTeam("Br", 100, 0, 0)
	teamB := genTeam("Br", 0, 100, 0)

	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		for ida := range teamA {
			wg.Add(1)
			go func(o *beat.One, mateTeam, targetTeam map[int32]*beat.One) {
				continuousWorks(o, mateTeam, targetTeam)
				wg.Done()
			}(teamA[ida], teamA, teamB)
		}
		wg.Done()
	}()
	go func() {
		for ida := range teamB {
			wg.Add(1)
			go func(o *beat.One, mateTeam, targetTeam map[int32]*beat.One) {
				continuousWorks(o, mateTeam, targetTeam)
				wg.Done()
			}(teamB[ida], teamB, teamA)
		}
		wg.Done()
	}()

	wg.Wait()
	log.Printf("all done, teamA survived:%d teamB survived:%d", countTeam(teamA), countTeam(teamB))
}

func genTeam(lv string, tNum, aNum, hNum int) map[int32]*beat.One {
	teamSize := tNum + aNum + hNum
	teamA := make(map[int32]*beat.One, teamSize)
	for i := 0; i < teamSize; i++ {
		a := beat.NewOne()
		a.UpToLv(lv)
		if i < tNum {
			a.Rotate(beat.RoleT)
		} else if i < tNum+aNum {
			a.Rotate(beat.RoleA)
		} else {
			a.Rotate(beat.RoleH)
		}
		a.RescueFull()
		a.StandBy()
		teamA[a.Id] = a
	}
	return teamA
}

func countTeam(team map[int32]*beat.One) int {
	n := 0
	for ida := range team {
		if team[ida].H > 0 {
			n++
		}
	}
	return n
}

func BotBattleThreatable(plv string, tNum, aNum, hNum int, npcLvRate float32, remark string) {
	tStart := time.Now()

	wg := sync.WaitGroup{}
	mates := genTeam(plv, tNum, aNum, hNum)

	bo1 := beat.NewOne()
	bo1.Tag = "bo1"
	bo1.UpAsBot(npcLvRate, tNum+aNum+hNum, BotTint)
	bo1.RescueFull()
	bo1.StandBy()

	bot := beat.NewBot(bo1)
	boc := NewDmgCollector(bo1, mates)
	defer boc.close()
	bo1.OutObserver = append(bo1.OutObserver, boc.onBsuffer)

	idx := 0
	for id := range mates {
		wg.Add(1)
		go func(a *beat.One, i int) {
			a.ThreatTo(bo1)
			a.OutObserver = append(a.OutObserver, boc.onPsuffer)
			continuousWork(a, mates, bo1)
			log.Printf("some one has gone, p(%d):%+v", i, a)
			wg.Done()
		}(mates[id], idx)
		idx++
	}

	go func() {
		bot.Start(mates)
		log.Printf("some one has gone, threatable bot stop:%+v", bo1)
	}()

	wg.Wait()
	matesNames := ""
	for i := range mates {
		matesNames += mates[i].ToString() + " "
	}
	log.Printf("all p done in threatable: %s", matesNames)
	useTime := time.Now().Sub(tStart).Seconds()
	time.Sleep(time.Millisecond * 1)
	bot.Shutdown()
	log.Printf("use %2.2f, team%s %v(%2d/%2d) VS bo %.2f(%.1f%%/%.0f), dps:%.1f aout:%.0f/%.0f bout:%.0f/%.0f dodge:%.3f/%.0f rsavg:%.1f/%.3f",
		useTime, remark, plv, countTeam(mates), len(mates), npcLvRate, (bo1.H / bo1.MaxH * 100), bo1.MaxH,
		float64(bo1.MaxH-bo1.H)/float64(len(mates))/useTime/100.0, boc.aout.wholeNum, boc.aout.sufferedSum/boc.aout.wholeNum, boc.a.wholeNum, boc.a.sufferedSum/boc.a.wholeNum, boc.t.dodgeNum/boc.t.wholeNum, boc.t.dodgeNum, boc.t.resistSum/boc.t.wholeNum, boc.t.resistSum/boc.t.sufferedSum)
}
func BotBattleThreatless(plv string, tNum, aNum, hNum int, npcLvRate float32, remark string) {
	tStart := time.Now()

	wg := sync.WaitGroup{}
	pAllNum := tNum + aNum + hNum
	wg.Add(pAllNum + 1)

	mates := genTeam(plv, tNum, aNum, hNum)

	bo1 := beat.NewOne()
	bo1.Tag = "bo1"
	bo1.UpAsBot(npcLvRate, tNum+aNum+hNum, BotTint)
	bo1.RescueFull()
	bo1.StandBy()
	boc := NewDmgCollector(bo1, mates)
	defer boc.close()
	bo1.OutObserver = append(bo1.OutObserver, boc.onPsuffer)

	idx := 0
	for id := range mates {
		go func(a *beat.One, i int) {
			// time.Sleep(time.Millisecond * 3)
			a.OutObserver = append(a.OutObserver, boc.onPsuffer)
			continuousWork(a, mates, bo1)
			log.Printf("some one has gone, a(%d) :%+v", i, a)
			wg.Done()
		}(mates[id], idx)
		idx++
	}

	go func() {
		// for i := range mates {
		// 	continuousA(bo1, mates[i])
		// }
		continuousAByBot(bo1, mates)
		log.Printf("some one has gone, threatless bot stop:%+v", bo1)
		wg.Done()
	}()

	wg.Wait()
	matesNames := ""
	for i := range mates {
		matesNames += mates[i].ToString() + " "
	}
	log.Printf("all p done in threatless: %s", matesNames)
	useTime := time.Now().Sub(tStart).Seconds()
	time.Sleep(time.Millisecond * 1)
	log.Printf("use %2.2f, team%s %v(%2d/%2d) VS bo %.2f(%.1f%%/%.0f), dps:%.1f aout:%.0f/%.0f bout:%.0f/%.0f dodge:%.3f/%.0f rsavg:%.1f/%.3f",
		useTime, remark, plv, countTeam(mates), len(mates), npcLvRate, (bo1.H / bo1.MaxH * 100), bo1.MaxH,
		float64(bo1.MaxH-bo1.H)/float64(len(mates))/useTime/100.0, boc.aout.wholeNum, boc.aout.sufferedSum/boc.aout.wholeNum, boc.a.wholeNum, boc.a.sufferedSum/boc.a.wholeNum, boc.t.dodgeNum/boc.t.wholeNum, boc.t.dodgeNum, boc.t.resistSum/boc.t.wholeNum, boc.t.resistSum/boc.t.sufferedSum)
}

func testRpcLv() {
	pNums := []int{1, 5, 10, 20, 25, 40, 100}
	for _, pNum := range pNums {
		rateList := []float32{beat.RateNpcA, beat.RateNpcB, beat.RateNpcC, beat.RateNpcD}
		maxHs := make([]float32, len(rateList))
		for i := range rateList {
			bo1 := beat.NewOne()
			bo1.UpAsBot(rateList[i], pNum, BotTint)
			//maxHs[i] = beat.UpBotMaxByRate(rateList[i], pNum, 0.6)
			maxHs[i] = bo1.MaxH
		}
		log.Printf("pnum=%d rate=%v  max=%v ", pNum, rateList, maxHs)
	}
}

type dmgCollector struct {
	id int32
	t  struct {
		wholeNum, dodgeNum, resistSum, armoredSum, sufferedSum float32
	}
	a struct {
		wholeNum, sufferedSum, hitSum, healedSum float32
	}
	aout struct {
		wholeNum, sufferedSum float32
	}
	bo struct {
		wholeNum, sufferedSum, hitSum float32
	}
	pin    chan beat.Dmg //player suffered
	bin    chan beat.Dmg //bo suffered
	done   chan bool
	closed bool
}

func NewDmgCollector(bo *beat.One, pmap map[int32]*beat.One) *dmgCollector {
	c := &dmgCollector{
		id:   bo.Id,
		pin:  make(chan beat.Dmg, 1000),
		bin:  make(chan beat.Dmg, 1000),
		done: make(chan bool),
	}
	go c.servePsuffer(bo, pmap)
	return c
}
func (c *dmgCollector) onPsuffer(d beat.Dmg) {
	if c.closed {
		// return
	}
	go func(d beat.Dmg) {
		c.pin <- d
	}(d)
}
func (c *dmgCollector) onBsuffer(d beat.Dmg) {
	if c.closed {
		// return
	}
	go func(d beat.Dmg) {
		c.bin <- d
	}(d)
}
func (c *dmgCollector) servePsuffer(bo *beat.One, pmap map[int32]*beat.One) {
	// log.Printf("dmg collector done")
	for {
		select {
		case d, open := <-c.pin:
			// log.Printf("collector(%d), a dmg come:%+v", c.id, d)
			if !open {
				log.Printf("wonder collector(%d) dry, d:%+v", c.id, d)
				continue
			}
			if d.To == bo.Id {
				// if _, ok := pmap[d.From]; !ok {
				// 	log.Printf("wonder, in collector(%d), a dmg not from pmap:%+v", c.id, d)
				// 	continue
				// }
				// if d.Tx == 1 && d.T == beat.DmgTypeHurt {
				// 	c.aout.wholeNum++
				// 	c.aout.sufferedSum += d.GetBeared()
				// }
				// if d.T == beat.DmgTypeHurt {
				// 	// c.bo.sufferedSum += d.GetBeared()
				// 	if d.GetBeared() == 0 {
				// 		// c.t.dodgeNum++
				// 	}
				// }
				continue
			}
			if _, ok := pmap[d.To]; !ok {
				log.Printf("wonder, in collector(%d), a dmg not to pmap:%+v", c.id, d)
				break
			}
			if pmap[d.To].Role == beat.RoleT && d.T == beat.DmgTypeHurt {
				c.t.wholeNum++
				c.t.sufferedSum += d.GetBeared()
				c.t.resistSum += d.GetResist()

				if d.GetResist() == d.P {
					c.t.dodgeNum++
				}
			}
			if pmap[d.To].Role == beat.RoleA && d.T == beat.DmgTypeHurt {
				c.a.wholeNum++
				c.a.sufferedSum += d.GetBeared()
			}
		case d, open := <-c.bin:
			if !open {
				log.Printf("wonder collector(%d) bin dry, d:%+v", c.id, d)
				continue
			}
			switch d.T {
			case beat.DmgTypeHurt:
				log.Printf("wonder collector(%d) bin got dmg, d:%+v", c.id, d)
				c.bo.sufferedSum += d.GetBeared()
				if d.Tx == 1 {
					c.aout.wholeNum++
					c.aout.sufferedSum += d.GetBeared()
				}
			}
		case <-c.done:
			return
		}
	}
}
func (c *dmgCollector) close() {
	c.closed = true
	close(c.done)
	close(c.pin)
}
